﻿var LoopModeler = LoopModeler || {};

LoopModeler.Matrix3 = function () {
    this.elements = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];

    this.setElement = function (t, u, val) {
        this.elements[t][u] = val;
    };

    this.getElement = function (t, u) {
        return this.elements[t][u];
    };

    this.det = function () {
        var m = this.elements;
        return m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2])
            - m[1][0] * (m[2][2] * m[0][1] - m[2][1] * m[0][2])
            + m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2]);
    };

    this.inverse = function () {
        var det = this.det();
        var matrix = new LoopModeler.Matrix3();
        var m = this.elements;
        matrix.setElement(0, 0, (m[1][1] * m[2][2] - m[2][1] * m[1][2]) / det);
        matrix.setElement(1, 0, (m[2][1] * m[0][2] - m[0][1] * m[2][2]) / det);
        matrix.setElement(2, 0, (m[0][1] * m[1][2] - m[1][1] * m[0][2]) / det);
        matrix.setElement(0, 1, (m[2][0] * m[1][2] - m[1][0] * m[2][2]) / det);
        matrix.setElement(1, 1, (m[0][0] * m[2][2] - m[2][0] * m[0][2]) / det);
        matrix.setElement(2, 1, (m[0][2] * m[1][0] - m[0][0] * m[1][2]) / det);
        matrix.setElement(0, 2, (m[1][0] * m[2][1] - m[2][0] * m[1][1]) / det);
        matrix.setElement(1, 2, (m[2][0] * m[0][1] - m[0][0] * m[2][1]) / det);
        matrix.setElement(2, 2, (m[0][0] * m[1][1] - m[1][0] * m[0][1]) / det);
        return matrix;
    };
};

LoopModeler.BezierFace = function () {
    this.points;

    this.initFromVectors = function (points) {
        points[12] = points[11].clone().subSelf(points[0]).addSelf(points[1]);
        points[13] = points[2].clone().subSelf(points[3]).addSelf(points[4]);
        points[14] = points[5].clone().subSelf(points[6]).addSelf(points[7]);
        points[15] = points[8].clone().subSelf(points[9]).addSelf(points[10]);

        this.points = points;
    };

    this.evaluate = function (t, u) {
        var point = new THREE.Vector3();
        var n = 3, m = 3;
        for (var i = 0; i <= n; i++)
            for (var j = 0; j <= m; j++) {
                var bi = bernstein(i, n) * Math.pow(t, i) * Math.pow(1 - t, n - i);
                var bj = bernstein(j, m) * Math.pow(u, j) * Math.pow(1 - u, m - j);
                var kij = getPointByIndexes(this.points, i, j);
                kij = kij.clone().multiplyScalar(bi * bj);
                point.addSelf(kij);
            }
        return point;
    };

    var pointMap = [0, 11, 10, 9, 1, 12, 15, 8, 2, 13, 14, 7, 3, 4, 5, 6];
    function getPointByIndexes(points, i, j) {
        return points[pointMap[i * 4 + j]];
    }

    function bernstein(i, n) {
        return factorial(n) / (factorial(i) * factorial(n - i));
    }

    function factorial(n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }

    this.tessellate = function (density) {
        var geometry = new THREE.Geometry();
        var vertexes = new Array();
        var triangles = new Array();
        for (var t = 0; t < density; t++)
            for (var u = 0; u < density; u++) {
                //todo: optimize to not repeat by looking to see if the index is populated
                vertexes[t * (density + 1) + u] = this.evaluate(t / density, u / density);
                vertexes[(t + 1) * (density + 1) + u] = this.evaluate((t + 1) / density, u / density);
                vertexes[t * (density + 1) + u + 1] = this.evaluate(t / density, (u + 1) / density);
                vertexes[(t + 1) * (density + 1) + u + 1] = this.evaluate((t + 1) / density, (u + 1) / density);

                geometry.faces.push(new THREE.Face3(t * (density + 1) + u, (t + 1) * (density + 1) + u, t * (density + 1) + u + 1));
                geometry.faces.push(new THREE.Face3(t * (density + 1) + u + 1, (t + 1) * (density + 1) + u, (t + 1) * (density + 1) + u + 1));
            }

        for (var i = 0; i < vertexes.length; i++) {
            geometry.vertices.push(vertexes[i]);
        }

        geometry.computeFaceNormals();
        //geometry.computeBoundingSphere();

        return geometry;
    };

    if (arguments[0] instanceof THREE.Vector3) {
        this.initFromVectors(arguments);
    }
};

LoopModeler.BezierFace.prototype.toString = function () {
    return 'Face results!';
};